package com.forsir.android.mhfinance.storeObjects;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.util.Log;
import android.widget.SimpleAdapter;
import android.widget.SimpleCursorAdapter;
import android.widget.SimpleExpandableListAdapter;

import com.forsir.android.mhfinance.R;
import com.forsir.android.mhfinance.helper.DbAdapter;

public class PaymentList {
	private final static String TAG = "MHFinance";

	public static boolean isChanged = false; // stores if payments is changed

	public static String currencyBefore = "";
	public static String currencyAfter = "";

	public static float lastSumWithdraw = 0;
	public static float lastSumDeposit = 0;

	public static Calendar firstDate = Calendar.getInstance();
	public static Calendar lastDate = Calendar.getInstance();

	private static List<Payment> mPayments = new ArrayList<Payment>();

	public static Payment getPayment(int position) {
		if (position < mPayments.size()) {
			return mPayments.get(position);
		}
		return null;
	}

	/**
	 * load first and last date
	 */
	public static void loadFirstAndLastDate(DbAdapter dbHelper) {
		final String[] columns = new String[] { "min(" + DbAdapter.PAYMENT_DATE + ") as firstDate", "max(" + DbAdapter.PAYMENT_DATE + ") as toDate" };
		final Cursor cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, columns, null, null, null, null, null);

		if (!cursor.moveToFirst()) {
			cursor.close();
			return;
		}

		firstDate.setTimeInMillis(cursor.getLong(0));
		lastDate.setTimeInMillis(cursor.getLong(1));
		cursor.close();

		if (firstDate.getTimeInMillis() == 0) {
			firstDate = Calendar.getInstance();
			lastDate = Calendar.getInstance();
		}
	}

	/**
	 * create list of payments
	 */
	public static SimpleAdapter getPaymentListAdapter(DbAdapter dbHelper, Context context, Resources resources, Account account, Subcategory subcategory,
	        Calendar dateFrom, Calendar dateTo) {
		final String[] from = new String[] { "id", "date", "category", "account", "amountWithdraw", "amountDeposit", "repeating", "remainder" };
		final int[] to = new int[] { R.id.report_list_id, R.id.report_list_date, R.id.report_list_category, R.id.report_list_account, R.id.report_list_amount_withdraw,
		        R.id.report_list_amount_deposit, R.id.report_list_repeating, R.id.report_list_remainder };
		final List<Map<String, String>> groupData = new ArrayList<Map<String, String>>();

		lastSumWithdraw = 0;
		lastSumDeposit = 0;
		Payment payment;

		final List<Payment> payments = loadPaymentsList(dbHelper, account, account, subcategory, dateFrom, dateTo, false);
		mPayments = new ArrayList<Payment>(payments.size() * 2);

		final int count = payments.size();
		for (int i = 0; i < count; i++) {
			payment = payments.get(i);

			payment.normalizeTransfer();
			if (account == null || payment.getAccount() == account) {
				groupData.add(generatePaymentMap(payment, resources, false));
				mPayments.add(payment);
			}
			if (payment.getType() == Payment.TYPE_TRANSFER_FROM) {
				if (account == null || payment.getTransferAccount() == account) {
					groupData.add(generatePaymentMap(payment, resources, true));
					mPayments.add(payment);
				}
			}
		}

		final SimpleAdapter adapter = new SimpleAdapter(context, groupData, R.layout.report_list_element, from, to);
		return adapter;
	}

	/**
	 * convert payment object to list of values
	 */
	private static Map<String, String> generatePaymentMap(Payment payment, Resources resources, boolean transfer) {
		final Map<String, String> curGroupMap = new HashMap<String, String>();

		float amount = payment.getAmount();
		if (transfer) {
			amount = -amount;
		}

		if (amount < 0) {
			lastSumWithdraw += amount;
		} else {
			lastSumDeposit += amount;
		}

		curGroupMap.put("id", String.valueOf(payment.getId()));
		// curGroupMap.put("date", payment.getDateString(resources) + (payment.isTimeZero() ? "" : " " + payment.getTimeString(resources)));
		curGroupMap.put("date", payment.getDateString(resources) + " " + payment.getTimeString(resources));
		curGroupMap.put("amountWithdraw", payment.getAmountStringWithdraw(currencyBefore, amount, currencyAfter));
		curGroupMap.put("amountDeposit", payment.getAmountStringDeposit(currencyBefore, amount, currencyAfter));
		if (payment.getSubcategory() == null) {
			if (transfer) {
				curGroupMap.put("category", resources.getString(R.string.type_transfer_to));
			} else {
				curGroupMap.put("category", resources.getString(R.string.type_transfer_from));
			}
		} else {
			curGroupMap.put("category", payment.getSubcategory().getFullName());
		}

		if (transfer) {
			curGroupMap.put("account", payment.getTransferAccount().getName());
		} else {
			curGroupMap.put("account", payment.getAccount().getName());
		}
		curGroupMap.put("repeating", (String) payment.getRepeating().getTypeShortString(resources));

		if (payment.getType() == Payment.TYPE_REMAINDER) {
			curGroupMap.put("remainder", resources.getString(R.string.to) + " " + Payment.formatAmountString(currencyBefore, payment.getRemainder(), currencyAfter));
		} else {
			curGroupMap.put("remainder", "");
		}
		return curGroupMap;
	}

	/**
	 * return list of payments by criteria in parameters
	 */
	public static List<Payment> loadPaymentsList(DbAdapter dbHelper, Account account, Account transferAccount, Subcategory subcategory, Calendar dateFrom,
	        Calendar dateTo, boolean withoutTransfer) {

		final String where = buildWhere(account, transferAccount, subcategory, dateFrom, dateTo, withoutTransfer);
		final Cursor cursor = dbHelper.fetchAllEntries(new Payment(), where, DbAdapter.PAYMENT_DATE + ", " + DbAdapter.ID);
		final List<Payment> payments = new ArrayList<Payment>();

		if (!cursor.moveToFirst()) {
			cursor.close();
			return payments;
		}

		Payment payment;
		do {
			payment = new Payment(cursor);
			payments.add(payment);
		} while (cursor.moveToNext());
		cursor.close();
		return payments;
	}

	// very quick but cannot configure date format
	public static SimpleCursorAdapter getCursorAdapterPaymentList(DbAdapter dbHelper, Context context, Account account, Subcategory subcategory, Calendar dateFrom,
	        Calendar dateTo) {
		final int[] to = new int[] { R.id.report_list_id, R.id.report_list_date, R.id.report_list_category, R.id.report_list_account, R.id.report_list_amount_withdraw,
		        R.id.report_list_amount_deposit, R.id.report_list_repeating };

		final String where = buildWhere(account, account, subcategory, dateFrom, dateTo, true);

		final String[] columns = new String[] { DbAdapter.PAYMENT_TABLE + "." + DbAdapter.ID + " as _id",
		        "date(" + DbAdapter.PAYMENT_DATE + ") as " + DbAdapter.PAYMENT_DATE, DbAdapter.CATEGORY_NAME, DbAdapter.ACCOUNT_NAME, DbAdapter.PAYMENT_WITHDRAW,
		        DbAdapter.PAYMENT_DEPOSIT, DbAdapter.PAYMENT_REPEATING };

		final String[] from = new String[] { "_id", DbAdapter.PAYMENT_DATE, DbAdapter.CATEGORY_NAME, DbAdapter.ACCOUNT_NAME, DbAdapter.PAYMENT_WITHDRAW,
		        DbAdapter.PAYMENT_DEPOSIT, DbAdapter.PAYMENT_REPEATING };

		final Cursor cursor = dbHelper.fetchEntry(DbAdapter.SELECT_PAYMENT_WITH_ACCOUNT, columns, where, null, null, null, DbAdapter.PAYMENT_DATE + ", "
		        + DbAdapter.PAYMENT_TABLE + "." + DbAdapter.ID);

		final SimpleCursorAdapter adapter = new SimpleCursorAdapter(context, R.layout.report_list_element, cursor, from, to);
		return adapter;
	}

	/**
	 * fill adapter for summary month
	 */
	public static SimpleAdapter getSummaryMonthListAdapter(DbAdapter dbHelper, Context context, Resources resources, Account account, Subcategory subcategory,
	        Calendar dateFrom, Calendar dateTo) {
		final String[] from = new String[] { "id", "month", "sum", "amountWithdraw", "amountDeposit" };
		final int[] to = new int[] { R.id.report_id, R.id.report_date, R.id.report_account, R.id.report_amount_withdraw, R.id.report_amount_deposit };
		final List<Map<String, String>> groupData = new ArrayList<Map<String, String>>();
		final boolean withoutTransfer = account == null;

		lastSumWithdraw = 0;
		lastSumDeposit = 0;

		final String[] columns = new String[] { DbAdapter.PAYMENT_MONTH + " as _id", "count(*) as count", "sum(" + DbAdapter.PAYMENT_WITHDRAW + ") as withdraw",
		        "sum(" + DbAdapter.PAYMENT_DEPOSIT + ") as deposit" };

		// select month, count(*), sum(withdraw), sum (deposit) from payment where (date in interval and account) group by month
		Cursor cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, columns, buildWhere(account, null, subcategory, dateFrom, dateTo, withoutTransfer), null, "month",
		        null, "month");

		final int[] withdraw = new int[12];
		final int[] deposit = new int[12];
		final int[] count = new int[12];
		int month;

		if (cursor.moveToFirst()) {
			do {
				month = cursor.getInt(0);
				count[month] = cursor.getInt(1);
				withdraw[month] = cursor.getInt(2);
				deposit[month] = cursor.getInt(3);
			} while (cursor.moveToNext());
		}
		cursor.close();

		if (account != null) {
			cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, columns, buildWhere(null, account, subcategory, dateFrom, dateTo, withoutTransfer), null, "month",
			        null, "month");
			if (cursor.moveToFirst()) {
				do {
					month = cursor.getInt(0);
					count[month] += cursor.getInt(1);
					// withdraw and deposit must be switched
					withdraw[month] -= cursor.getInt(3);
					deposit[month] -= cursor.getInt(2);
				} while (cursor.moveToNext());
			}
			cursor.close();
		}

		for (int i = 0; i < 12; i++) {
			final Map<String, String> curGroupMap = new HashMap<String, String>();

			curGroupMap.put("id", String.valueOf(i));
			curGroupMap.put("month", resources.getStringArray(R.array.months)[i]);
			curGroupMap.put("count", String.valueOf(count[i]));
			curGroupMap.put("amountWithdraw", Payment.formatAmountString(currencyBefore, withdraw[i], currencyAfter));
			curGroupMap.put("amountDeposit", Payment.formatAmountString(currencyBefore, deposit[i], currencyAfter));
			curGroupMap.put("sum", Payment.formatAmountString(currencyBefore, withdraw[i] + deposit[i], currencyAfter));

			groupData.add(curGroupMap);

			lastSumWithdraw += withdraw[i];
			lastSumDeposit += deposit[i];
		}

		final SimpleAdapter adapter = new SimpleAdapter(context, groupData, R.layout.report_summary_element, from, to);
		return adapter;
	}

	/**
	 * fill adapter for showing category summary
	 */
	public static SimpleExpandableListAdapter getPaymentCategorySummaryAdapter(DbAdapter dbHelper, Context context, Account account, Calendar dateFrom, Calendar dateTo) {
		CategoryList.cleanSummary();

		final String where = buildWhere(account, null, null, dateFrom, dateTo, true);

		final String[] columns = new String[] { DbAdapter.PAYMENT_CATEGORY + " as _id", "count(*) as count", "total(" + DbAdapter.PAYMENT_DEPOSIT + ") as deposit",
		        "total(" + DbAdapter.PAYMENT_WITHDRAW + ") as withdraw", };

		final Cursor cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, columns, where, null, DbAdapter.PAYMENT_CATEGORY, null, DbAdapter.PAYMENT_CATEGORY);
		CategoryList.fillChildSummary(convertCursor(cursor, 4));
		cursor.close();

		final String[] from = new String[] { "id", "name", "count", "summary", "withdraw", "deposit" };

		final int[] to = new int[] { R.id.report_id, R.id.report_category, R.id.report_count, R.id.report_summary, R.id.report_amount_withdraw,
		        R.id.report_amount_deposit };

		lastSumWithdraw = CategoryList.getSumWithdraw();
		lastSumDeposit = CategoryList.getSumDeposit();

		return new SimpleExpandableListAdapter(context, CategoryList.getMainGroup(currencyBefore, currencyAfter), R.layout.report_category_element, from, to,
		        CategoryList.getChildGroup(currencyBefore, currencyAfter), R.layout.report_subcategory_element, from, to);
	}

	/**
	 * fill adapter for showing account summary
	 */
	public static SimpleAdapter getAccountSummaryAdapter(DbAdapter dbHelper, Context context, Subcategory subcategory, Calendar dateFrom, Calendar dateTo) {
		AccountsList.cleanSummary();

		String where = buildWhere(null, null, subcategory, dateFrom, dateTo, false);
		String[] columns = new String[] { DbAdapter.PAYMENT_ACCOUNT + " as _id", "count(*) as count", "total(" + DbAdapter.PAYMENT_DEPOSIT + ") as deposit",
		        "total(" + DbAdapter.PAYMENT_WITHDRAW + ") as withdraw", };
		Cursor cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, columns, where, null, DbAdapter.PAYMENT_ACCOUNT, null, DbAdapter.PAYMENT_ACCOUNT);
		AccountsList.fillSummary(convertCursor(cursor, 4));
		cursor.close();

		where = buildWhere(null, null, subcategory, dateFrom, dateTo, false);
		// withdraw and deposit must be switched
		columns = new String[] { DbAdapter.PAYMENT_TRANSFER_ACCOUNT + " as _id", "count(*) as count", "total(-" + DbAdapter.PAYMENT_WITHDRAW + ") as deposit",
		        "total(-" + DbAdapter.PAYMENT_DEPOSIT + ") as withdraw", };
		cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, columns, where, null, DbAdapter.PAYMENT_TRANSFER_ACCOUNT, null, DbAdapter.PAYMENT_TRANSFER_ACCOUNT);
		AccountsList.fillSummary(convertCursor(cursor, 4));
		cursor.close();

		final String[] from = new String[] { "id", "name", "count", "summary", "withdraw", "deposit" };

		final int[] to = new int[] { R.id.report_id, R.id.report_account, R.id.report_count, R.id.report_summary, R.id.report_amount_withdraw, R.id.report_amount_deposit };

		lastSumWithdraw = AccountsList.getSumWithdraw();
		lastSumDeposit = AccountsList.getSumDeposit();

		return new SimpleAdapter(context, AccountsList.getGroup(currencyBefore, currencyAfter), R.layout.report_account_element, from, to);
	}

	/**
	 * convert cursor to hash map of values. First value of cursor must be id
	 */
	private static HashMap<Long, String[]> convertCursor(Cursor cursor, int columns) {
		final HashMap<Long, String[]> list = new HashMap<Long, String[]>();

		if (!cursor.moveToFirst()) {
			cursor.close();
			return list;
		}

		do {
			final String[] values = new String[columns];
			for (int i = 1; i < columns; i++) {
				values[i - 1] = cursor.getString(i);
			}
			list.put(cursor.getLong(0), values);
		} while (cursor.moveToNext());

		return list;
	}

	/**
	 * create where string for select from given parameters
	 */
	private static String buildWhere(Account account, Account accountTransferTo, Subcategory subcategory, Calendar dateFrom, Calendar dateTo, boolean withoutTransfer) {
		final StringBuilder where = new StringBuilder(8);
		where.append(dateFrom.getTimeInMillis() + "<=" + DbAdapter.PAYMENT_DATE + " and " + DbAdapter.PAYMENT_DATE + "<" + dateTo.getTimeInMillis());

		if (account != null) {
			if (accountTransferTo != null) {
				where.append(" and (" + DbAdapter.PAYMENT_ACCOUNT + " = " + account.getId() + " or " + DbAdapter.PAYMENT_TRANSFER_ACCOUNT + " = "
				        + accountTransferTo.getId() + ")");
			} else {
				where.append(" and " + DbAdapter.PAYMENT_ACCOUNT + " = " + account.getId());
			}
		} else {
			if (accountTransferTo != null) {
				where.append(" and " + DbAdapter.PAYMENT_TRANSFER_ACCOUNT + " = " + accountTransferTo.getId());
			}
		}

		if (withoutTransfer) {
			where.append(" and " + DbAdapter.PAYMENT_TABLE + "." + DbAdapter.PAYMENT_CATEGORY + " is not null");
		}

		if (subcategory != null) {
			where.append(" and " + DbAdapter.PAYMENT_CATEGORY + " = " + subcategory.getId());
		}
		return where.toString();
	}

	public static String getLastSumWithdrawFormated() {
		return Payment.formatAmountString(currencyBefore, lastSumWithdraw, currencyAfter);
	}

	public static String getLastSumDepositFormated() {
		return Payment.formatAmountString(currencyBefore, lastSumDeposit, currencyAfter);
	}

	public static CharSequence getLastSumFormated() {
		return Payment.formatAmountString(currencyBefore, lastSumDeposit + lastSumWithdraw, currencyAfter);
	}

	/**
	 * return true if payment on position is first
	 */
	public static boolean isFirst(int position) {
		return mPayments.get(position).getId() == mPayments.get(0).getId();
	}

	/**
	 * return true if payment on position is last
	 */
	public static boolean isLast(int position) {
		return mPayments.get(position).getId() == mPayments.get(mPayments.size() - 1).getId();
	}

	public static int movePrevious(int position) {
		if (isFirst(position)) {
			return position;
		}
		final long paymentId = mPayments.get(position).getId();
		position--;
		if (paymentId == mPayments.get(position).getId()) {
			position--;
		}
		return position;
	}

	public static int moveNext(int position) {
		if (isLast(position)) {
			return position;
		}
		final long paymentId = mPayments.get(position).getId();
		position++;
		if (paymentId == mPayments.get(position).getId()) {
			position++;
		}
		return position;
	}

	/**
	 * update remainders in interval
	 */
	public static void updateIntervalRemainder(long dateFrom, long dateTo, long accountId, DbAdapter dbHelper) {
		final Payment transferPayment = new Payment();
		// get reminders
		final String where = DbAdapter.PAYMENT_TYPE + "=" + Payment.TYPE_REMAINDER + " and " + DbAdapter.PAYMENT_DATE + ">=" + dateFrom + " and "
		        + DbAdapter.PAYMENT_ACCOUNT + "=" + accountId + " and " + DbAdapter.PAYMENT_DATE + "<=" + dateTo;
		final Cursor cursor = dbHelper.fetchEntry(DbAdapter.PAYMENT_TABLE, transferPayment.getColumns(), where, null, "", "", DbAdapter.PAYMENT_DATE);
		if (!cursor.moveToFirst()) {
			cursor.close();
			return;
		}

		// update all remainders, stores first payments
		Payment paymentFrom = null;
		do {
			transferPayment.fillFromCursor(cursor);
			Log.d(TAG, "updateIntervalRemainder - transferPayment:" + transferPayment.getDateString());
			if (paymentFrom == null) {
				Log.d(TAG, "updateIntervalRemainder - paymentFrom:null");
			} else {
				Log.d(TAG, "updateIntervalRemainder - paymentFrom:" + paymentFrom.getDateString());
			}
			transferPayment.computeRemainder(paymentFrom, dbHelper);
			dbHelper.updateEntry(transferPayment);
			if (paymentFrom == null) {
				paymentFrom = new Payment();
			}
			paymentFrom.copyPayment(transferPayment);
		} while (cursor.moveToNext());
		cursor.close();

		transferPayment.updateNextRemainder(dbHelper);
	}

	/**
	 * update remainders in payment repeating
	 */
	public static void updateRepeatingRemainder(Payment payment1, Payment payment2, DbAdapter dbHelper) {
		if (payment2 == null) {
			updateIntervalRemainder(payment1.getDateLong(), payment1.getRepeating().getEndDateLong(), payment1.getAccount().getId(), dbHelper);
			return;
		}

		if (payment1.getAccount().getId() != payment2.getAccount().getId()) {
			updateIntervalRemainder(payment1.getDateLong(), payment1.getRepeating().getEndDateLong(), payment1.getAccount().getId(), dbHelper);
			updateIntervalRemainder(payment2.getDateLong(), payment2.getRepeating().getEndDateLong(), payment2.getAccount().getId(), dbHelper);
			return;
		}

		final long dateFrom = Math.min(payment1.getDateLong(), payment2.getDateLong());
		final long dateTo = Math.max(payment1.getRepeating().getEndDateLong(), payment2.getRepeating().getEndDateLong());
		updateIntervalRemainder(dateFrom, dateTo, payment1.getAccount().getId(), dbHelper);
	}
}
